//
//  HUMOpenGLBaseRender.swift
//  OpenGLDemo1
//
//  Created by Ming on 2018/9/28.
//  Copyright © 2018年 Ming. All rights reserved.
//

import UIKit

class HUMOpenGLBaseRender {
    private var context: EAGLContext?
    private let scale: CGFloat
    
    private var renderWidth: GLint                      = 0
    private var renderHeight: GLint                     = 0
    private var viewFrameBuffer: GLuint                 = 0
    private var viewRenderBuffer: GLuint                = 0
    private var viewDepthRenderBuffer: GLuint           = 0
    private var multiSampleFrameBuffer: GLuint          = 0
    private var multiSampleRenderBuffer: GLuint         = 0
    private var multiSampleDepthRenderBuffer: GLuint    = 0
    
    deinit {
        destroyFramebuffer()
        context = nil
    }
    
    init?() {
        context = EAGLContext(api: .openGLES3)
        scale = UIScreen.main.scale
        if !EAGLContext.setCurrent(context) {
            print("set current context failed")
            return nil
        }
    }
    
    // MARK: Render process
    func willRender() {
        if EAGLContext.current() != context {
            EAGLContext.setCurrent(context)
        }
        glBindFramebuffer(GLenum(GL_FRAMEBUFFER), multiSampleFrameBuffer)
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), multiSampleRenderBuffer)
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
    }
    
    func render() {
    }
    
    func didRender() {
        if UIApplication.shared.applicationState != .active {
            return
        }
        glBindFramebuffer(GLenum(GL_DRAW_FRAMEBUFFER), viewFrameBuffer)
        glBindFramebuffer(GLenum(GL_READ_FRAMEBUFFER), multiSampleFrameBuffer)
        // OpenGL ES 3.0 Core multisampling
        // Discard the depth buffer from the read fbo. It is no more necessary.
        glInvalidateFramebuffer(GLenum(GL_READ_FRAMEBUFFER), 1, [GLenum(GL_DEPTH_ATTACHMENT)])
        // Copy the read fbo(multisampled framebuffer) to the draw fbo(single-sampled framebuffer)
        glBlitFramebuffer(0, 0, renderWidth, renderHeight, 0, 0, renderWidth, renderHeight, GLbitfield(GL_COLOR_BUFFER_BIT), GLenum(GL_NEAREST))
        glInvalidateFramebuffer(GLenum(GL_READ_FRAMEBUFFER), 1, [GLenum(GL_COLOR_ATTACHMENT0)])
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), viewRenderBuffer)
        context?.presentRenderbuffer(Int(GL_RENDERBUFFER))
    }
    
    func resize(from layer: CAEAGLLayer) -> Bool {
        destroyFramebuffer()
        glGenFramebuffers(1, &viewFrameBuffer)
        glBindFramebuffer(GLenum(GL_FRAMEBUFFER), viewFrameBuffer)
        glGenRenderbuffers(1, &viewRenderBuffer)
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), viewRenderBuffer)
        context?.renderbufferStorage(Int(GL_RENDERBUFFER), from: layer)
        glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), viewRenderBuffer)
        
        glGetRenderbufferParameteriv(GLenum(GL_RENDERBUFFER), GLenum(GL_RENDERBUFFER_WIDTH), &renderWidth)
        glGetRenderbufferParameteriv(GLenum(GL_RENDERBUFFER), GLenum(GL_RENDERBUFFER_HEIGHT), &renderHeight)
        
        glGenRenderbuffers(1, &viewDepthRenderBuffer)
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), viewDepthRenderBuffer)
        glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_DEPTH_COMPONENT16), renderWidth, renderHeight)
        glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_DEPTH_ATTACHMENT), GLenum(GL_RENDERBUFFER), viewDepthRenderBuffer)
        
        glGenFramebuffers(1, &multiSampleFrameBuffer)
        glBindFramebuffer(GLenum(GL_FRAMEBUFFER), multiSampleFrameBuffer)
        glGenRenderbuffers(1, &multiSampleRenderBuffer)
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), multiSampleRenderBuffer)
        glRenderbufferStorageMultisampleAPPLE(GLenum(GL_RENDERBUFFER), 4, GLenum(GL_RGBA8), renderWidth, renderHeight)
        glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), multiSampleRenderBuffer)
        
        glGenRenderbuffers(1, &multiSampleDepthRenderBuffer)
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), multiSampleDepthRenderBuffer)
        glRenderbufferStorageMultisample(GLenum(GL_RENDERBUFFER), 4, GLenum(GL_DEPTH_COMPONENT16), renderWidth, renderHeight)
        glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_DEPTH_ATTACHMENT), GLenum(GL_RENDERBUFFER), multiSampleDepthRenderBuffer)
        
        if glCheckFramebufferStatus(GLenum(GL_FRAMEBUFFER)) != GLenum(GL_FRAMEBUFFER_COMPLETE) {
            print("failed to make complete framebuffer object: %x", glCheckFramebufferStatus(GLenum(GL_FRAMEBUFFER)))
            return false
        }
        
        glViewport(0, 0, renderWidth, renderHeight)
        glClearColor(0.0, 0.0, 0.0, 0.0)
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
        return true
    }
}

extension HUMOpenGLBaseRender {
    private func showOpenGLInfo() {
        var config: [(String, GLint)] = []
        var param: GLint = 0
        glGetIntegerv(GLenum(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS), &param)
        config.append(("Max vertex texture image units", param))
        glGetIntegerv(GLenum(GL_MAX_TEXTURE_IMAGE_UNITS), &param)
        config.append(("Max texture image units", param))
        glGetIntegerv(GLenum(GL_MAX_TEXTURE_SIZE), &param)
        config.append(("Max texture size", param))
        
        config.forEach { (item) in
            let (key, value) = item
            print("\(key): \(value)")
        }
    }
    private func destroyFramebuffer() {
        if viewFrameBuffer != 0 {
            glDeleteFramebuffers(1, &viewFrameBuffer)
            viewFrameBuffer = 0
        }
        if viewRenderBuffer != 0 {
            glDeleteRenderbuffers(1, &viewRenderBuffer)
            viewRenderBuffer = 0
        }
        if viewDepthRenderBuffer != 0 {
            glDeleteRenderbuffers(1, &viewDepthRenderBuffer)
            viewDepthRenderBuffer = 0
        }
        if multiSampleFrameBuffer != 0 {
            glDeleteFramebuffers(1, &multiSampleFrameBuffer)
            multiSampleFrameBuffer = 0
        }
        if multiSampleRenderBuffer != 0 {
            glDeleteRenderbuffers(1, &multiSampleRenderBuffer)
            multiSampleRenderBuffer = 0
        }
        if multiSampleDepthRenderBuffer != 0 {
            glDeleteRenderbuffers(1, &multiSampleDepthRenderBuffer)
            multiSampleDepthRenderBuffer = 0
        }
    }
}
